Modernes ABAP – Ein Beispiel

Die Neuerungen im ABAP-Umfeld sind inzwischen bereits einige Jahre alt. Über den VALUE Operator, der mit Release 7.40 eingeführt wurde, hat Horst Keller bereits 2013 gebloggt. Trotzdem werden einige der neuen Befehle nur sparsam eingesetzt. Das liegt einerseits daran, dass man sich wirklich an die Verwendung gewöhnen muss, andererseits ist man vielleicht unsicher, welche Gefahren (Performance) sie bergen. Ich persönlich finde zudem, dass ein übermäßiger Einsatz der Befehle, die sehr vielfältig untereinander geschachtelt werden können, auch schnell nicht mehr schön aussehen und zudem schwer zu überblicken sind.

Aber um mal wieder einen Beitrag zu schreiben und weil es vielleicht doch elegant ist, eine komplexe Aufgabe in nur einer Zeile zu lösen, stelle ich dir heute folgende Lösung vor.

Interne Tabelle kopieren

Nehmen wir an, wir hätten eine Tabelle mit einer Materialnummer, einer gewünschten Menge (QTY) und der verfügbaren Menge (AVQ). Für die Anzeige im SALV-Grid soll die Tabelle noch um den Materialkurztext erweitert werden und Einträge, bei denen die gewünschte Menge von der verfügbaren Menge abweicht, sollen farblich gekennzeichnet werden.

Datenstrukturen

Die Strukturen der Quelltabelle (SRC) und der Zieltabelle (TGT) sehen wie folgt aus:

  TYPES: BEGIN OF _src,
           mat TYPE c LENGTH 1, "material number
           qty TYPE i, "requested qty
           avq TYPE i, "available qty
         END OF _src,
         _src_t TYPE STANDARD TABLE OF _src WITH EMPTY KEY,

         BEGIN OF _tgt,
           mat TYPE c LENGTH 1,
           qty TYPE i,
           avq TYPE c LENGTH 2,
           txt TYPE maktx,
           col TYPE lvc_t_scol,
         END OF _tgt,
         _tgt_t TYPE STANDARD TABLE OF _tgt WITH EMPTY KEY.

Testdaten

Die Tabelle, die ich in die Ausgabetabelle für das SALV-Grid kopieren möchte, wird mit ein paar Testdaten gefüllt:

  DATA(src) = VALUE _src_t(
      ( mat = 'A' qty = 10 avq = 10 )
      ( mat = 'B' qty = 20 avq = 15 )
      ( mat = 'C' qty = 30 avq = 30 ) ).

Hilfsklasse

Mit der folgenden Hilfsklasse wird der Text zu einem Material ermittelt:

CLASS mat DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS text
      IMPORTING mat          TYPE char1
      RETURNING VALUE(descr) TYPE string.
ENDCLASS.

CLASS mat IMPLEMENTATION.
  METHOD text.
    descr = SWITCH #( mat
      WHEN 'A' THEN `Antenna`
      WHEN 'B' THEN `Brain`
      WHEN 'C' THEN `Case`
      ELSE `unknown` ).
  ENDMETHOD.
ENDCLASS.

Herkömmliche Vorgehensweise

Das althergebrachte Coding ohne neue ABAP-Features könnte wie folgt aussehen:

  DATA tgt1     TYPE _tgt_t.
  DATA tgt_line TYPE _tgt.
  DATA col_line TYPE lvc_s_scol.

  LOOP AT src INTO DATA(src_line).
    CLEAR tgt_line.
    MOVE-CORRESPONDING src_line TO tgt_line.
    tgt_line-txt = mat=>text( tgt_line-mat ).
    IF tgt_line-qty <> tgt_line-avq.
      CLEAR col_line.
      col_line-fname = space.
      col_line-color-col = 6.
      APPEND col_line TO tgt_line-col.
    ENDIF.
    append tgt_line to tgt1.
  ENDLOOP.

Neue Vorgehensweise

Mit Hilfe der neuen ABAP-Features VALUE, COND, FOR und CORRESPONDING habe ich die folgende Lösung erarbeitet:

  DATA(tgt) = VALUE _tgt_t( FOR line IN src (
      VALUE #( BASE CORRESPONDING #( line )
      txt = mat=>text( line-mat )
      col = COND #( LET color = VALUE lvc_t_scol( ) IN
                WHEN line-qty <> line-avq 
                  THEN VALUE #( ( fname = '' color-col = 6 ) ) )
      ) ) ).

Auffällig sind folgende Dinge:

  • Das Coding ist deutlich kürzer
  • Es sind keine Variablendeklarationen notwendig
  • Es sieht einigermaßen konfus aus

Schön ist auf jeden Fall, dass mit Hilfe eines Befehls, bzw. einer Befehlskette, Daten von einer Tabelle in eine andere kopiert werden können und sozusagen nebenbei weitere Feldmanipulationen vorgenommen werden können.

VALUE

Den Value-Befehl habe ich lieben gelernt, denn er macht es in vielfältigen Situationen einfach, Daten in eine Struktur oder Tabelle einzufügen. Und zwar ohne dass eine Datendeklaration notwendig wäre.

Der Value-Befehl wird direkt gefolgt von der Typendefinition, die verwendet werden soll. Wenn die Typendefinition implizit ermittelbar ist, zum Beispiel, weil die Daten an eine bereits definierte Variable übergeben werden, dann reicht die Angabe des “#”. In meinem Beispiel möchte ich aber gerade die Datendefinition durch den VALUE-Befehl definieren, also gebe ich den zu verwendenden Tabellentyp an:

DATA(tgt) VALUE _tgt_t( ).

FOR

In der VALUE-Angabe führe ich einen LOOP über die Quelltabelle aus und kopiere die Felder der Quelltabelle in die Zieltabelle mittels CORRESPONDING:

  DATA(tgt) = VALUE _tgt_t( 
    FOR line IN src (
      CORRESPONDING #( line ) ) ).

Der Befehl FOR line IN src entspricht also in etwa dem Befehl:

LOOP AT src INTO DATA(line).

CORRESPONDING

Eine Herausforderung war es, zusätzlich zu CORRESPONDING noch weitere Felder anderweitig belegen zu können. Das folgende Coding funktioniert nämlich nicht:

DATA(tgt) = VALUE _tgt_t( 
  FOR line IN src (
    CORRESPONDING #( line )
    txt = mat=>text( line-mat ) ) ).

Hier muss mit einer erneuten VALUE-Operation gearbeitet werden:

DATA(tgt) = VALUE _tgt_t(
  FOR line IN src (
    VALUE #( BASE CORRESPONDING #( line )
    txt = mat=>text( line-mat ) ) ) ).

COND

Nun haben wir bereits die Tabelle kopiert und zusätzlich den Materialtext dazu gelesen. Zusätzlich möchte ich noch die COLOR-Tabelle füllen, wenn sich die angeforderte Menge von der verfügbaren Menge unterscheidet. Diese Anforderung habe ich mit COND realisiert:

col = COND #( LET color = VALUE lvc_t_scol( ) IN
        WHEN line-qty <> line-avq
          THEN VALUE #( ( fname = '' color-col = 6 ) ) )

Alleine diesen Befehl finde ich deutlich komplexer als eine zuvor ausgeführten IF-Anweisung. COND ist allerdings notwendig, wenn der Code Inline ausgeführt werden soll. Zudem ist er sehr mächtig, denn es können verschiedene Bedingungen abgefragt werden. Er entspricht also in etwa einer verschachtelten IF – ELSEIF – ELSE Struktur.

SWITCH

Den Switch-Befehl, der in etwa einer CASE-Anweisung entspricht, habe ich nicht mehr in der Kopier-Anweisung unter bekommen… Die Arbeitsweise lässt sich jedoch gut in der Hilfsmethode MAT=>TEXT( ) ersehen.

Ausgabe

Die Ausgabe der aufbereiteten Tabelle erfolgt mit Hilfe des SALV-Grid:

  TRY.
      cl_salv_table=>factory(
        IMPORTING
          r_salv_table   = DATA(salv)
        CHANGING
          t_table        = tgt ).
      DATA(cols) = salv->get_columns( ).
      cols->set_color_column( 'COL' ).
      salv->display( ).
    CATCH cx_salv_msg.
      MESSAGE 'error salv' TYPE 'I'.
  ENDTRY.
Ausgabe der kopierten Tabelle

Fazit

Zu der vorgestellten Lösung und allgemein möchte ich folgendes anmerken:

Formatierung

Formatierung ist alles!

Die neuen ABAP-Features sind so komplex und können im Grunde endlos verschachtelt werden. Deswegen ist es notwendig, den Quelltext so zu formatieren, dass deutlich wird, welche Befehle und Sequenzen zusammengehören. Erschwerend kommt hinzu, dass für die neuen Befehle kein Pretty-Print möglich ist. Man muss also selber entscheiden, was noch in eine Zeile passt und was wie weit eingerückt werden sollte.

Wenn das obige Coding sinnlos formatiert wird, dann sieht es wirklich sehr unübersichtlich aus:

DATA(tgt) = VALUE _tgt_t( FOR line IN src (  VALUE #( BASE 
      CORRESPONDING #( line )  txt = mat=>text( line-mat )
          col = COND #( LET color =  VALUE lvc_t_scol( ) 
        IN WHEN line-qty <> line-avq 
             THEN VALUE #( ( fname = '' color-col = 6 ) ) ) ) ) ).

Anzahl der Verwendungen

Wenn die Zuweisungen oder Ermittlungen, die durch die neuen ABAP-Features gemacht werden sozusagen einmalig sind, dann sind sie eine elegante Möglichkeit, die Programmierung kürzer zu machen. Sobald die Ergebnisse jedoch vielschichtiger werden oder die Abfragen komplexer, dann ist es sinnvoll, die entsprechenden Anweisungen entweder vorab berechnet oder in Funktionen ausgelagert werden.

Debugging

Was man immer im Hinterkopf behalten sollte ist, dass das Debuggen komplexer Anweisungsketten deutlich erschwert wird. Man kann zwar im Debugger die Schrittweite setzen, die ein Debuggen der Einzelteile ermöglicht, allerdings ist dies sehr mühsam. Es kann kein Break-Point innerhalb einer Anweisungskette gesetzt werden.

Anwendung

Auf jeden Fall sollte man sich mit den neuen Befehlen beschäftigen und diese in die tägliche Arbeit einfließen lassen. Es übt und erleichtert in vielen Fällen die Arbeit. Nur so lernt man, fremden Code zu verstehen und wann und wie man die neuen Befehle selber am sinnvollsten einsetzt.

Eine gute Möglichkeit, um auf dem Laufenden zu bleiben und auch um die Anwendung der ABAP-Features zu verstehen, ist, Horst Keller auf blogs.sap.com zu folgen.

Enno Wulff